package net.xinshi.converter;

import net.xinshi.commons.log.BytesLoggingWrapper;
import net.xinshi.domain.ApiRequest;
import net.xinshi.domain.JsonRequest;
import net.xinshi.exception.RestApiValidationRejectedException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.util.TypeUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Map.Entry;

public class JsonRequestConverter extends MappingJackson2HttpMessageConverter {
    private static final String OPENAPI_JSON_HEADER_NODE_NAME = "REQUEST_ATTRS";
    public static final String OPENAPI_JSON_REQDATA_NODE_NAME = "REQUEST_DATA";

    // API编码
    private static final String OPENAPI_JSON_HEADER_API_ID = "Api_ID";
    // APP_TOKEN
    private static final String OPENAPI_JSON_HEADER_APP_TOKEN = "App_Token";
    // API版本号
    // 时间戳，格式为yyyy-mm-dd HH:mm:ss:SSS，时区为GMT+8
    private static final String OPENAPI_JSON_HEADER_TIME_STAMP = "Time_Stamp";
    // 生成服务请求签名字符串所使用的算法类型，目前仅支持MD5，[参见第5章5.6节]
    private static final String OPENAPI_JSON_HEADER_SIGN_METHOD = "Sign_Method";
    // 服务请求的签名字符串
    private static final String OPENAPI_JSON_HEADER_SIGN = "Sign";
    // 服务请求的签名字符串
    private static final String OPENAPI_JSON_HEADER_SERIAL_NO = "Serial_No";

    private final Logger logger = LogManager.getLogger(getClass());
    private final boolean isLoggerDebugEnabled = logger.isDebugEnabled();
    private final ObjectMapper jsonMapper = new ObjectMapper();

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        Class<?> targetClass = (type instanceof Class<?> ? (Class<?>) type : null);
        if (targetClass == null) {
            ResolvableType resolvableType = ResolvableType.forType(type);
            targetClass = resolvableType.resolve();
        }
        return readInternal(targetClass, inputMessage);
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        byte[] reqBytes = StreamUtils.copyToByteArray(inputMessage.getBody());

        Logger logger;
        Class<?> controlerClz = CurrentControllerMethod.getCurrentControllerClz();
        if (null != controlerClz) {
            logger = LogManager.getLogger(controlerClz);
        } else {
            logger = this.logger;
        }

        if (logger.isDebugEnabled()) {
            Method method = CurrentControllerMethod.getCurrentControllerMethod();
            if (null != method) {
                logger.debug("{}() Original Input: {}", method.getName(), BytesLoggingWrapper.wrap(reqBytes));
            } else {
                logger.debug("Original Input: {}", BytesLoggingWrapper.wrap(reqBytes));
            }
        }
        if (TypeUtils.isAssignable(ApiRequest.class, clazz)) {
            ApiRequest parsedPointsRequest = readPointsJsonRequest(clazz, reqBytes, inputMessage);
            if (null != parsedPointsRequest) {
                return parsedPointsRequest;
            } else {
                inputMessage = new RestoredPointsInputMessage(inputMessage, reqBytes);
                return super.readInternal(clazz, inputMessage);
            }
        } else if (TypeUtils.isAssignable(JsonRequest.class, clazz)) {
            JsonRequest parsedPointsRequest = readJsonRequest(clazz, reqBytes, inputMessage);
            if (null != parsedPointsRequest) {
                return parsedPointsRequest;
            } else {
                inputMessage = new RestoredPointsInputMessage(inputMessage, reqBytes);
                return super.readInternal(clazz, inputMessage);
            }
        } else {
            return super.readInternal(clazz, inputMessage);
        }
    }

    /**
     * 对Controller JSON请求参数类型为ApiParams子类型的请求进行解析。
     *
     * @param pointsRequestType
     * @param inputMessage
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     * @author wengyongyi
     */
    private ApiRequest readPointsJsonRequest(Class<? extends Object> pointsRequestType, byte[] reqBytes,
                                             HttpInputMessage inputMessage) {
        JsonNode openapiRootReq = null;
        try {
            openapiRootReq = jsonMapper.readTree(reqBytes);
        } catch (Exception e) {
            if (isLoggerDebugEnabled) {
                logger.debug("当前请求无法按照OpenAPI RESTFUL请求进行解析，将视为非OpenAPI请求进行处理. 解析错误：{}", e.getMessage());
            }
            return null;
        }

        if (null == openapiRootReq) {
            if (isLoggerDebugEnabled) {
                logger.debug("当前JSON请求不符合OpenAPI请求数据结构（缺少REQUEST节点），将视为非OpenAPI请求进行处理");
            }
            return null;
        }

        JsonNode openapiHeader = openapiRootReq.get(OPENAPI_JSON_HEADER_NODE_NAME);
        JsonNode reqData = openapiRootReq.get(OPENAPI_JSON_REQDATA_NODE_NAME);
        if (null == openapiHeader) {
            if (isLoggerDebugEnabled) {
                logger.debug("当前JSON请求不符合OpenAPI请求数据结构（REQUEST_ATTRS节点），将视为非OpenAPI请求进行处理");
            }
            throw new RestApiValidationRejectedException("错误的OpenAPI请求，缺少OpenAPI公共参数报文头");
        }
        if (null == reqData) {
            if (isLoggerDebugEnabled) {
                logger.debug("当前JSON请求不符合OpenAPI请求数据结构（缺少REQUEST_DATA节点），将视为非OpenAPI请求进行处理");
            }
            throw new RestApiValidationRejectedException("错误的OpenAPI请求，缺少业务参数报文体");
        }

        // W提取OpenAPI请求中的公共参数
        ApiRequest openApiParams = new ApiRequest();
        Iterator<Entry<String, JsonNode>> headerIte = openapiHeader.fields();
        while (headerIte.hasNext()) {
            Entry<String, JsonNode> headerField = headerIte.next();
            switch (headerField.getKey()) {
                case OPENAPI_JSON_HEADER_API_ID:
                    openApiParams.setApiId(readHeaderFieldValue(headerField.getValue()));
                    break;
                case OPENAPI_JSON_HEADER_APP_TOKEN:
                    openApiParams.setAppToken(readHeaderFieldValue(headerField.getValue()));
                    break;
                case OPENAPI_JSON_HEADER_TIME_STAMP:
                    openApiParams.setTimestamp(readHeaderFieldValue(headerField.getValue()));
                    break;
                case OPENAPI_JSON_HEADER_SIGN_METHOD:
                    openApiParams.setSignMethod(readHeaderFieldValue(headerField.getValue()));
                    break;
                case OPENAPI_JSON_HEADER_SIGN:
                    openApiParams.setSign(readHeaderFieldValue(headerField.getValue()));
                    break;
                case OPENAPI_JSON_HEADER_SERIAL_NO:
                    openApiParams.setTransactionUuid(readHeaderFieldValue(headerField.getValue()));
                    break;
                default:
                    logger.warn("***请留意***OpenAPI请求中出现集成规范文档约定之外的公共参数名：{}", headerField.getKey());
            }
        }

        openApiParams.setOriginRequest(openapiRootReq);
        openApiParams.setRequestData(reqData.toString());
        return openApiParams;
    }


    /**
     * 对Controller JSON请求参数类型为ApiParams子类型的请求进行解析。
     *
     * @param pointsRequestType
     * @param inputMessage
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     * @author wengyongyi
     */
    private JsonRequest readJsonRequest(Class<? extends Object> pointsRequestType, byte[] reqBytes,
                                        HttpInputMessage inputMessage) {
        JsonNode openapiRootReq = null;
        try {
            openapiRootReq = jsonMapper.readTree(reqBytes);
        } catch (Exception e) {
            if (isLoggerDebugEnabled) {
                logger.debug("当前请求无法按照JSON请求进行解析，将视为非JSON请求进行处理. 解析错误：{}", e.getMessage());
            }
            return null;
        }
        if (null == openapiRootReq) {
            if (isLoggerDebugEnabled) {
                logger.debug("当前JSON请求不符合请求数据结构，将视为非JSON请求进行处理");
            }
            return null;
        }
        try {
            JsonRequest pointsReq = (JsonRequest) jsonMapper.treeToValue(openapiRootReq, pointsRequestType);
            return pointsReq;
        } catch (UnrecognizedPropertyException e) {
            throw new RestApiValidationRejectedException("请求中包含未能识别的业务参数名" + e.getPropertyName(), e);
        } catch (JsonProcessingException e) {
            throw new RestApiValidationRejectedException("错误的JSON请求，无法解析请求中的JSON数据结构", e);
        }
    }

    /**
     * 获取OpenAPI公共参数的值，如果为NULL则返回空字符串""。
     *
     * @return
     * @author wengyongyi
     */
    private String readHeaderFieldValue(JsonNode headerFieldValue) {
        if (null != headerFieldValue) {
            return headerFieldValue.asText("");
        }
        return "";
    }

    /**
     * @author <a href="mailto:wengyongyi@crc.com.hk">wengyongyi</a>
     * @version 1.0.0
     * @description 恢复请求数据流
     * @since 2016年9月24日
     */
    public static class RestoredPointsInputMessage implements HttpInputMessage {
        private HttpInputMessage origHttpInputMessage;
        private byte[] origRequestByets;

        public RestoredPointsInputMessage(HttpInputMessage origHttpInputMessage, byte[] origRequestByets) {
            super();
            this.origHttpInputMessage = origHttpInputMessage;
            this.origRequestByets = origRequestByets;
        }

        @Override
        public HttpHeaders getHeaders() {
            return origHttpInputMessage.getHeaders();
        }

        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(origRequestByets);
        }
    }

}
